/* mbed Microcontroller Library
 * Copyright (c) 2006-2013 ARM Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "TimerEvent.h"
#include "device.h"

/* Ticker frequency = 1kHz, 1ms resolution. */
#define FTICKER 1000

typedef void (*tickerEventHandler)(TimerEvent *id);

static T16registers * const timer = MODULE_T1;
static volatile uint16_t ticks = 0;
static volatile uint16_t match = 0;
static tickerEventHandler eventHandler = NULL;
static struct TickerEvent_t *head = NULL;

#define set_timer_interrupt(timestamp) (match = timestamp, IEC0bits.T1IE = 1)

#define disable_timer_interrupt() (IEC0bits.T1IE = 0)

#define clear_timer_flag() (IFS0bits.T1IF = 0)

static void init(void) {
    timer->CON.word = 0x00;     /* Stop timer and reset control register. */
    timer->TMRA = 0x00;         /* Clear timer value register. */
    timer->PRA = FCY / FTICKER; /* Preload the period register. */
    IPC0bits.T1IP = 0x01;       /* Setup timer interrupt priority level. */
    IFS0bits.T1IF = 0;          /* Clear the timer interrupt status flag. */
    timer->CON.bits.TON = 1;    /* Start timer, prescaler 1:1 and Tcy. */
}

static void setEventHandler(tickerEventHandler handler) {
    init();
    eventHandler = handler;
}

static void insertTickerEvent(
        struct TickerEvent_t *ev, TimerEvent *id, unsigned int timestamp) {
    _disable_interrupts();
    ev->id = id;
    ev->timestamp = timestamp;
    struct TickerEvent_t *prev, *p;
    for(prev = NULL, p = head; p != NULL; prev = p, p = p->next) {
        if((int)(timestamp - p->timestamp) <= 0) {
            break; /* This event should come before p. */
        }
    }
    if(prev == NULL) {
        head = ev; /* Append event as head. */
        set_timer_interrupt(timestamp);
    } else {
        prev->next = ev; /* Append event in between. */
    }
    ev->next = p; /* Hook tail. */
    _enable_interrupts();
}

static void removeTickerEvent(struct TickerEvent_t *ev) {
    _disable_interrupts();
    if(head == ev) {
        head = ev->next; /* Replace event by its successor. */
        if(ev->next != NULL) {
            set_timer_interrupt(head->timestamp);
        }
    } else {
        struct TickerEvent_t *p;
        for(p = head; p != NULL; p = p->next) {
            if(p->next == ev) {
                p->next = ev->next; /* Hook successor to previous event. */
                break;
            }
        }
    }
    _enable_interrupts();
}

static void interruptHandler(void) {
    while(1) {
        if(head == NULL) {
            disable_timer_interrupt(); /* No more events left. */
            return;
        }
        if((int)(head->timestamp - ticks) <= 0) {
            struct TickerEvent_t *p = head;
            head = head->next;
            if(eventHandler != NULL) {
                eventHandler(p->id); /* Execute current event. */
            }
        } else {
            set_timer_interrupt(head->timestamp); /* Enable future events. */
            return;
        }
    }
}

void __attribute__((interrupt,auto_psv)) _T1Interrupt(void) {
    ticks++;
    interruptHandler();
    clear_timer_flag();
}

VTABLE(TimerEvent, Object)
    VMETHOD(Object, dtor) = (void (*)(Object *))&_TimerEvent_dtor;
    VMETHOD(TimerEvent, dtor) = (void (*)(TimerEvent *))&_TimerEvent_dtor;
    VMETHOD(TimerEvent, onEvent) = (void (*)(TimerEvent *))&Object_virtual;
VTABLE_END

void _TimerEvent_ctor(TimerEvent *obj) {
    _Object_ctor(&obj->super);
    VHOOK(TimerEvent);
    setEventHandler(&_TimerEvent_interrupt);
}

void _TimerEvent_dtor(TimerEvent *obj) {
    _TimerEvent_remove(obj);
    _Object_dtor(&obj->super);
}

void _TimerEvent_insert(TimerEvent *obj, unsigned int timestamp) {
    insertTickerEvent(&obj->_event, obj, timestamp);
}

void _TimerEvent_remove(TimerEvent *obj) {
    removeTickerEvent(&obj->_event);
}

void _TimerEvent_interrupt(TimerEvent *id) {
    VIRTUAL(TimerEvent, onEvent, id)VIRTUAL_END;
}
